<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    var p = new Promise(function (resolve, reject) {
      //做一些异步操作
      setTimeout(function () {
        console.log('执行完成0');
        resolve('随便什么数据');
      }, 2000);
    });
    //运行代码，会在2秒后输出“执行完成”。注意！我只是new了一个对象，并没有调用它，我们传进去的函数就已经执行了，这是需要注意的一个细节。所以我们用Promise的时候一般是包在一个函数中，在需要的时候去运行这个函数如：
    function runAsync() {
      var p1 = new Promise(function (resolve, reject) {
        //做一些异步操作
        setTimeout(function () {
          console.log('执行完成p1');
          resolve('随便什么数据p1');
        }, 2000);
      });
      return p1;
    }
    runAsync() //需要的时候去运行这个函数
    console.log(runAsync()) //Promise对象

    //在我们包装好的函数最后，会return出Promise对象，也就是说，执行这个函数我们得到了一个Promise对象。还记得Promise对象上有then、catch方法吧？这就是强大之处了，看下面的代码：

    runAsync().then(function (data) {
      console.log(data);
      //后面可以用传过来的数据做些其他操作
      //......
    });

    //在runAsync()的返回上直接调用then方法，then接收一个参数，是函数，并且会拿到我们在runAsync中调用resolve时传的的参数。运行这段代码，会在2秒后输出“执行完成”，紧接着输出“随便什么数据”。

    //原来then里面的函数就跟我们平时的回调函数一个意思，能够在runAsync这个异步任务执行完成之后被执行。这就是Promise的作用了，简单来讲，就是能把原来的回调写法分离出来，在异步操作执行完后，用链式调用的方式执行回调函数。

    //Promise就这点能耐？我把回调函数封装一下，给runAsync传进去不也一样吗，就像这样：
    function runAsyncCb(callback) {
      setTimeout(function () {
        console.log('执行完成callback');
        callback('随便什么数据callback');
      }, 2000);
    }

    runAsyncCb(function (data) {
      console.log(data);
    });
    //效果也是一样的，还费劲用Promise干嘛。那么问题来了，有多层回调该怎么办？如果callback也是一个异步操作，而且执行完后也需要有相应的回调函数，该怎么办呢？总不能再定义一个callback2，然后给callback传进去吧。而Promise的优势在于，可以在then方法中继续写Promise对象并返回，然后继续调用then来进行回调操作。

    //链式操作的用法
    // 所以，从表面上看，Promise只是能够简化层层回调的写法，而实质上，Promise的精髓是“状态”，用维护状态、传递状态的方式来使得回调函数能够及时调用，它比传递callback函数要简单、灵活的多。所以使用Promise的正确场景是这样的：
    function runAsync1() {
      var p = new Promise(function (resolve, reject) {
        //做一些异步操作
        setTimeout(function () {
          console.log('异步任务1执行完成');
          resolve('随便什么数据1');
        }, 1000);
      });
      return p;
    }

    function runAsync2() {
      var p = new Promise(function (resolve, reject) {
        //做一些异步操作
        setTimeout(function () {
          console.log('异步任务2执行完成');
          resolve('随便什么数据2');
        }, 2000);
      });
      return p;
    }

    function runAsync3() {
      var p = new Promise(function (resolve, reject) {
        //做一些异步操作
        setTimeout(function () {
          console.log('异步任务3执行完成');
          resolve('随便什么数据3');
        }, 2000);
      });
      return p;
    }

    // runAsync1()
    //   .then(function (data) {
    //     console.log(data);
    //     return runAsync2();
    //   })
    //   .then(function (data) {
    //     console.log(data);
    //     return runAsync3();
    //   })
    //   .then(function (data) {
    //     console.log(data);
    //   });

    //在then方法中，你也可以直接return数据而不是Promise对象，在后面的then中就可以接收到数据了，比如我们把上面的代码修改成这样：
    runAsync1()
      .then(function (data) {
        console.log(data);
        return runAsync2();
      })
      .then(function (data) {
        console.log(data); //异步任务2执行完成  随便什么数据2
        return '直接返回数据'; //这里直接返回数据
      })
      .then(function (data) {
        console.log(data); //'直接返回数据'
      });

    //reject的用法
    //reject呢，它是做什么的呢？事实上，我们前面的例子都是只有“执行成功”的回调，还没有“失败”的情况，reject的作用就是把Promise的状态置为rejected，这样我们在then中就能捕捉到，然后执行“失败”情况的回调。看下面的代码。
    function getNumber() {
      var p = new Promise(function (resolve, reject) {
        //做一些异步操作
        setTimeout(function () {
          var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
          if (num <= 5) {
            resolve(num);
          } else {
            reject('数字太大了');
          }
        }, 2000);
      });
      return p;
    }
    //.then(resolve, rejected)
    getNumber()
      .then(
        function (data) {
          console.log('resolved=>');
          console.log(data);
        },
        function (reason, data) {
          console.log('rejected=>');
          console.log(reason);
        }
      );
    // getNumber函数用来异步获取一个数字，2秒后执行完成，如果数字小于等于5，我们认为是“成功”了，调用resolve修改Promise的状态。否则我们认为是“失败”了，调用reject并传递一个参数，作为失败的原因。
    // 运行getNumber并且在then中传了两个参数，then方法可以接受两个参数，第一个对应resolve的回调，第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码，你会随机得到下面两种结果：

    //catch的用法
    //我们知道Promise对象除了then方法，还有一个catch方法，它是做什么用的呢？其实它和then的第二个参数一样，用来指定reject的回调，用法是这样：
    getNumber()
      .then(function (data) {
        console.log('resolved:');
        console.log(data);
      })
      .catch(function (reason) {
        console.log('rejected:');
        console.log(reason);
      });
    //效果和写在then的第二个参数里面一样。不过它还有另外一个作用：在执行resolve的回调（也就是上面then中的第一个参数）时，如果抛出异常了（代码出错了），那么并不会报错卡死js，而是会进到这个catch方法中。请看下面的代码：
    getNumber()
      .then(function (data) {
        console.log('resolved');
        console.log(data);
        console.log(somedata); //此处的somedata未定义
      })
      .catch(function (reason) {
        console.log('rejected');
        console.log(reason);
      });
    //在resolve的回调中，我们console.log(somedata);而somedata这个变量是没有被定义的。如果我们不用Promise，代码运行到这里就直接在控制台报错了，不往下运行了。但是在这里，会得到这样的结果：
    //也就是说进到catch方法里面去了，而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了，这与我们的try/catch语句有相同的功能。

    //all的用法
    //Promise的all方法提供了并行执行异步操作的能力，并且在所有异步操作执行完后才执行回调。我们仍旧使用上面定义好的runAsync1、runAsync2、runAsync3这三个函数，看下面的例子：
    Promise
      .all([runAsync1(), runAsync2(), runAsync3()])
      .then(function (results) {
        console.log(results); // ["随便什么数据1", "随便什么数据2", "随便什么数据3"]
      });
    //用Promise.all来执行，all接收一个数组参数，里面的值最终都算返回Promise对象。这样，三个异步操作的并行执行的，等到它们都执行完后才会进到then里面。那么，三个异步操作返回的数据哪里去了呢？都在then里面呢，all会把所有异步操作的结果放进一个数组中传给then，就是上面的results。所以上面代码的输出结果就是：

    //有了all，你就可以并行执行多个异步操作，并且在一个回调中处理所有的返回数据，是不是很酷？有一个场景是很适合用这个的，一些游戏类的素材比较多的应用，打开网页时，预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后，我们再进行页面的初始化。

    //race的用法
    //all方法的效果实际上是「谁跑的慢，以谁为准执行回调」，那么相对的就有另一个方法「谁跑的快，以谁为准执行回调」，这就是race方法，这个词本来就是赛跑的意思。race的用法与all一样，我们把上面runAsync1的延时改为1秒来看一下：
    Promise
      .race([runAsync1(), runAsync2(), runAsync3()])
      .then(function (results) {
        console.log(results);
      });
    //这三个异步操作同样是并行执行的。结果你应该可以猜到，1秒后runAsync1已经执行完了，此时then里面的就执行了。结果是这样的：
    //你猜对了吗？不完全，是吧。在then里面的回调开始执行时，runAsync2()和runAsync3()并没有停止，仍旧再执行。于是再过1秒后，输出了他们结束的标志。



    //这个race有什么用呢？使用场景还是很多的，比如我们可以用race给某个异步请求设置超时时间，并且在超时后执行相应的操作，代码如下：
    //请求某个图片资源
    function requestImg() {
      var p = new Promise(function (resolve, reject) {
        var img = new Image();
        img.onload = function () {
          resolve(img);
        }
        img.src = 'xxxxxx';
      });
      return p;
    }

    //延时函数，用于给请求计时
    function timeout() {
      var p = new Promise(function (resolve, reject) {
        setTimeout(function () {
          reject('图片请求超时');
        }, 5000);
      });
      return p;
    }

    Promise
      .race([requestImg(), timeout()])
      .then(function (results) {
        console.log(results);//requestImg()跑赢执行requestImg()
      })
      .catch(function (reason) {
        console.log(reason);//timeout()跑赢执行timeout()
      });
      //requestImg函数会异步请求一张图片，我把地址写为"xxxxxx"，所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race，于是他俩就会赛跑，如果5秒之内图片请求成功了，那么遍进入then方法，执行正常的流程。如果5秒钟图片还未成功返回，那么timeout就跑赢了，则进入catch，报出“图片请求超时”的信息。运行结果如下：
  </script>
</body>

</html>